package com.huxi.lang.object.support;

import static java.lang.reflect.Modifier.isAbstract;
import static java.lang.reflect.Modifier.isStatic;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import com.huxi.lang.object.spi.ReflectFieldAnnotationParse;

public class ReflectObjectBuilder {

	@SuppressWarnings("rawtypes")
	private Map<Class, ReflectFieldAnnotationParse> parsers = new HashMap<>();
	
	public ReflectObjectBuilder() {
		
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected void appendAnnotations(ReflectFieldBuilder builder, Field field) {
		Class<?> type = field.getClass();
		for (Annotation an : field.getAnnotations()) {
			ReflectFieldAnnotationParse ap = parsers.get(an.getClass());
			ap.parse(an, builder, type);
		}
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected void appendAnnotations(ReflectFieldBuilder builder, Method method) {
		Class<?> type = method.getParameterTypes()[0];
		for (Annotation an : method.getAnnotations()) {
			ReflectFieldAnnotationParse ap = parsers.get(an.getClass());
			ap.parse(an, builder, type);
		}
		for (Annotation an : method.getParameterAnnotations()[0]) {
			ReflectFieldAnnotationParse ap = parsers.get(an.getClass());
			ap.parse(an, builder, type);
		}
	}

	protected Map<String, ReflectFieldBuilder> appendFields(Map<String, ReflectFieldBuilder> map, Field[] fields,
			Class<?> clss) {
		for (Field fd : fields) {
			int mod = fd.getModifiers();
			if (isStatic(mod)) {
				continue;
			}
			ReflectFieldBuilder builder = map.get(fd.getName());
			if (builder == null) {
				builder = new ReflectFieldBuilder(fd.getName());
				map.put(fd.getName(), builder);
			}
			if (builder.mergeField(fd)) {
				appendAnnotations(builder, fd);
				builder.merged();
			}
		}
		return map;
	}

	protected static String stardandFieldName(String name, int start) {
		StringBuilder sb = new StringBuilder(name.length());
		sb.append(Character.toLowerCase(name.charAt(start))).append(name, start + 1, name.length());
		return sb.toString();
	}

	protected static boolean isSelfReturn(Method method) {
		return method.getReturnType().isAssignableFrom(method.getDeclaringClass());
	}

	protected static boolean isSupperTop(Class<?> clss) {
		return clss == null || clss == Object.class;
	}

	protected Map<String, ReflectFieldBuilder> appendMethods(Map<String, ReflectFieldBuilder> map, Method[] methods,
			Class<?> clss) {
		for (Method md : methods) {
			int mod = md.getModifiers();
			if (isStatic(mod) || isAbstract(mod)) {
				continue;
			}
			int ct = md.getParameterCount();
			if (0 == ct) {
				String nm = md.getName();
				if (nm.startsWith("is")) {
					if (nm.length() == 2 || md.getReturnType() != boolean.class) {
						continue;
					}
					nm = stardandFieldName(nm, 2);
				} else if (nm.startsWith("get")) {
					if (nm.length() == 3 || md.getReturnType() == void.class) {
						continue;
					}
					nm = stardandFieldName(nm, 3);
				} else {
					continue;
				}
				ReflectFieldBuilder builder = map.get(nm);
				if (builder == null) {
					builder = new ReflectFieldBuilder(nm);
					map.put(nm, builder);
				}
				if (builder.mergeGetMethod(md)) {
					appendAnnotations(builder, md);
					builder.merged();
				}
			} else if (1 == ct) {
				String nm = md.getName();
				if (nm.startsWith("set")) {
					if (nm.length() == 3 || !(md.getReturnType() == void.class || isSelfReturn(md))) {
						continue;
					}
					nm = stardandFieldName(nm, 3);
				} else if (nm.startsWith("with")) {
					if (nm.length() == 3 || !isSelfReturn(md)) {
						continue;
					}
					nm = stardandFieldName(nm, 4);
				} else {
					continue;
				}
				ReflectFieldBuilder builder = map.get(nm);
				if (builder == null) {
					builder = new ReflectFieldBuilder(nm);
					map.put(nm, builder);
				}
				if (builder.mergeSetMethod(md)) {
					appendAnnotations(builder, md);
					builder.merged();
				}
			}
		}
		return map;
	}

	protected Map<String, ReflectFieldBuilder> publicOf(Class<?> clss) {
		Objects.requireNonNull(clss);
		Map<String, ReflectFieldBuilder> map = new HashMap<>();
		appendMethods(map, clss.getMethods(), clss);
		if (!clss.isInterface()) {
			appendFields(map, clss.getFields(), clss);
		}
		return map;
	}

	protected Map<String, ReflectFieldBuilder> allOf(Class<?> clss, boolean onlySelf) {
		Objects.requireNonNull(clss);
		Map<String, ReflectFieldBuilder> map = new HashMap<>();
		if (clss.isInterface()) {
			appendMethods(map, clss.getMethods(), clss);
			return map;
		}
		appendMethods(map, clss.getDeclaredMethods(), clss);
		appendFields(map, clss.getDeclaredFields(), clss);
		Class<?> pclass = clss.getSuperclass();
		if (onlySelf) {
			if (!isSupperTop(pclass)) {
				appendMethods(map, clss.getMethods(), clss);
				appendFields(map, clss.getFields(), clss);
			}
		} else {
			while (!isSupperTop(pclass)) {
				appendMethods(map, pclass.getDeclaredMethods(), clss);
				appendFields(map, pclass.getDeclaredFields(), clss);
				pclass = pclass.getSuperclass();
			}
		}
		return map;
	}

	public <T> ReflectObject<T> build(Class<T> clazz) throws IllegalAccessException {
		Map<String, ReflectFieldBuilder> map = publicOf(clazz);
		Map<String, ReflectField> fields = new HashMap<>();
		Lookup lookup = MethodHandles.lookup();
		for (ReflectFieldBuilder fb : map.values()) {
			ReflectField rf = fb.build(lookup);
			if (rf != null) {
				fields.put(rf.getField(), rf);
			}
		}
		return new ReflectObject<>(clazz, fields);
	}

	public <T> ReflectObject<T> build(Class<T> clazz, boolean allOnlySelf) throws IllegalAccessException {
		Map<String, ReflectFieldBuilder> map = allOf(clazz, allOnlySelf);
		Map<String, ReflectField> fields = new HashMap<>();
		Lookup lookup = MethodHandles.lookup();
		for (ReflectFieldBuilder fb : map.values()) {
			ReflectField rf = fb.build(lookup);
			if (rf != null) {
				fields.put(rf.getField(), rf);
			}
		}
		return new ReflectObject<>(clazz, fields);
	}

}
